﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using $prjsn$.Common;


namespace $safeprojectname$.RestApi
{
    /// <summary>
    /// http://stackoverflow.com/a/7605119/1679310
    /// 
    /// Extract
    /// When you do the POST in step 2, your browser will send a "OPTIONS" method to the server. 
    /// This is a "sniff" by the browser to see if the server is cool with you POSTing to it. 
    /// The server responds with an "Access-Control-Allow-Origin" telling the browser its OK 
    /// to POST|GET|ORIGIN if request originated from "http://from.com" or "https://from.com". 
    /// Since the server is OK with it, the browser will make a 2nd request (this time a POST). 
    /// It is good practice to have your client set the content type 
    /// it is sending - so you'll need to allow that as well.
    /// 
    /// 
    /// 
    ///1) Your server will have to handle 2 requests per operation
    ///2) You will have to think about the security implications. Be careful before doing something like 'Access-Control-Allow-Origin: *'
    ///3) This wont work on mobile browsers. In my experience they do not allow cross domain POST at all. I've tested android, iPad, iPhone
    ///4) There is a pretty big bu g in FF &lt; 3.6 where if the server returns a non 400 response code AND there is a response body (validation errors for example), FF 3.6 wont get the response body. 
    /// This is a huge pain in the ass, since you cant use good REST practices. See bu g here (its filed under jQuery, but my guess is its a FF bu g - seems to be fixed in FF4).
    ///5) Always return the headers above, not just on OPTION requests. FF needs it in the response from the POST.
    /// 
    /// 
    /// 
    /// Helpful links
    /// 
    /// 
    /// http://stackoverflow.com/a/7605119/1679310
    /// http://stackoverflow.com/q/13018020/1679310
    /// 
    /// </summary>
    public class CrossDomainDelegationHandler : DelegatingHandler
    {
        const string Origin = "Origin";
        const string AccessControlRequestMethod = "Access-Control-Request-Method";
        const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
        const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
        const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
        const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // would we support CORS at all? see Project.config
            if (!Project.Settings.AllowCrossDomain)
            {
                return base.SendAsync(request, cancellationToken);
            }

            // is Cross domain?
            bool isCorsRequest = request.Headers.Contains(Origin);
            if (!isCorsRequest)
            {
                return base.SendAsync(request, cancellationToken);
            }

            // is it Negotiation?
            bool isPreflightRequest = request.Method == HttpMethod.Options;
            if (!isPreflightRequest)
            {
                return base.SendAsync(request, cancellationToken).ContinueWith(t =>
                {
                    HttpResponseMessage resp = t.Result;
                    resp.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
                    return resp;
                });
            }

            return Task.Factory.StartNew(() =>
            {
                HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());

                string accessControlRequestMethod = request.Headers.GetValues(AccessControlRequestMethod).FirstOrDefault();
                if (accessControlRequestMethod != null)
                {
                    response.Headers.Add(AccessControlAllowMethods, accessControlRequestMethod);
                }

                string requestedHeaders = string.Join(", ", request.Headers.GetValues(AccessControlRequestHeaders));
                if (!string.IsNullOrEmpty(requestedHeaders))
                {
                    response.Headers.Add(AccessControlAllowHeaders, requestedHeaders);
                }

                return response;
            }, cancellationToken);
        }
    }
}
